// Fluxions 3D Engine
// matrix3.hpp
// Copyright (C) 2000-2011 Jonathan Metzgar
// 
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#ifndef FLUXIONS_MATRIX3_HPP
#define FLUXIONS_MATRIX3_HPP

#include "math.hpp"

namespace Fluxions
{
	template <typename X>
	class TMatrix3
	{
	public:
		union
		{
			// standard form representation
			struct
			{
				X _11, _12, _13;
				X _21, _22, _23;
				X _31, _32, _33;
			};
			
			// normal access
			struct
			{
				X m00, m01, m02;
				X m10, m11, m12;
				X m20, m21, m22;
			};			
			
			// transposed access
			struct
			{
				X t00, t10, t20;
				X t01, t11, t21;
				X t02, t12, t22;
			};
			
			// array form
			struct
			{
				X m[9];
			};
			
			// 2D array form
			struct
			{
				X mm[3][3];
			};
		};
		
		TMatrix3();
		TMatrix3(
			const X a00, const X a10, const X a20,
			const X a01, const X a11, const X a21,
			const X a02, const X a12, const X a22);
		TMatrix3(const TMatrix3<X> & M);
		TMatrix3(const X M[3][3]);
		TMatrix3(const X M[9]);
		const TMatrix3<X> & operator = (const TMatrix3<X> & M);

		template <typename T> operator TMatrix3<T> () const;
		
		void LoadIdentity();
		void MultMatrix(const TMatrix3<X> & M);
		const TMatrix3<X> & operator *= (const TMatrix3<X> & M);
		const TMatrix3<X> & operator += (const TMatrix3<X> & M);
		const TMatrix3<X> & operator -= (const TMatrix3<X> & M);

		const TMatrix3<X> operator * (const TMatrix3<X> & M) const;
		const TMatrix3<X> operator + (const TMatrix3<X> & M) const;
		const TMatrix3<X> operator - (const TMatrix3<X> & M) const;
		
		template <typename T> const TMatrix3<X> operator * (const T &x) const;
		template <typename T> const TMatrix3<X> operator / (const T &x) const;
		template <typename T> const TMatrix3<X> &operator *= (const T &x);
		template <typename T> const TMatrix3<X> &operator /= (const T &x);
		
		template <typename T> void Rotate(const T angle);
		template <typename T> void Scale(const T x, const T y);
		
		const X Determinant() const;
		bool Invert();
		void Transpose();
		void Adjugate();
	};
	
	//////////////////////////////////////////////////////////////////
	// I M P L E M E N T A T I O N ///////////////////////////////////
	//////////////////////////////////////////////////////////////////

	template <typename X>
	TMatrix3<X>::TMatrix3()
	{
	}

	template <typename X>
	TMatrix3<X>::TMatrix3(
		const X a00, const X a10, const X a20,
		const X a01, const X a11, const X a21,
		const X a02, const X a12, const X a22
		)
	{
		m00 = a00; m10 = a10; m20 = a20;
		m01 = a01; m11 = a11; m21 = a21;
		m02 = a02; m12 = a12; m22 = a22;
	}

	template <typename X>
	TMatrix3<X>::TMatrix3(const TMatrix3<X> & M)
	{
		_11 = M._11; _21 = M._21; _31 = M._31;
		_12 = M._12; _22 = M._22; _32 = M._32;
		_13 = M._13; _23 = M._23; _33 = M._33;		
	}

	template <typename X>
	TMatrix3<X>::TMatrix3(const X M[3][3])
	{
		for (int i = 0; i < 3; i++)
		{
			for (int j = 0; j < 3; j++)
			{
				mm[i][j] = M[j][i];
			}
		}
	}

	template <typename X>
	TMatrix3<X>::TMatrix3(const X M[9])
	{
		for (int i = 0; i < 9; i++)
		{
			m[i] = M[i];
		}
	}

	template <typename X>
	const TMatrix3<X> & TMatrix3<X>::operator = (const TMatrix3<X> & M)
	{
		_11 = M._11; _21 = M._21; _31 = M._31;
		_12 = M._12; _22 = M._22; _32 = M._32;
		_13 = M._13; _23 = M._23; _33 = M._33;
		return *this;
	}

	template <typename X>
	template <typename T>
	TMatrix3<X>::operator TMatrix3<T> () const
	{
		return TMatrix3<T> (m00, m10, m20, m01, m11, m21, m02, m12, m22);
	}

	template <typename X>
	void TMatrix3<X>::LoadIdentity()
	{
		_11 = 1; _21 = 0; _31 = 0;
		_12 = 0; _22 = 1; _32 = 0;
		_13 = 0; _23 = 0; _33 = 1;
	}

	template <typename X>
	void TMatrix3<X>::MultMatrix(const TMatrix3<X> & M)
	{
		TMatrix3<X> u;
		u.m00 = m00 * M.m00 + m01 * M.m10 + m02 * M.m20;
		u.m01 = m00 * M.m01 + m01 * M.m11 + m02 * M.m21;
		u.m02 = m00 * M.m02 + m01 * M.m12 + m02 * M.m22;
		u.m10 = m10 * M.m00 + m11 * M.m10 + m12 * M.m20;
		u.m11 = m10 * M.m01 + m11 * M.m11 + m12 * M.m21;
		u.m12 = m10 * M.m02 + m11 * M.m12 + m12 * M.m22;
		u.m20 = m20 * M.m00 + m21 * M.m10 + m22 * M.m20;
		u.m21 = m20 * M.m01 + m21 * M.m11 + m22 * M.m21;
		u.m22 = m20 * M.m02 + m21 * M.m12 + m22 * M.m22;
		*this = u;
	}

	template <typename X>
	const TMatrix3<X> & TMatrix3<X>::operator *= (const TMatrix3<X> & M)
	{
		TMatrix3<X> u;
		u.m00 = m00 * M.m00 + m01 * M.m10 + m02 * M.m20;
		u.m01 = m00 * M.m01 + m01 * M.m11 + m02 * M.m21;
		u.m02 = m00 * M.m02 + m01 * M.m12 + m02 * M.m22;
		u.m10 = m10 * M.m00 + m11 * M.m10 + m12 * M.m20;
		u.m11 = m10 * M.m01 + m11 * M.m11 + m12 * M.m21;
		u.m12 = m10 * M.m02 + m11 * M.m12 + m12 * M.m22;
		u.m20 = m20 * M.m00 + m21 * M.m10 + m22 * M.m20;
		u.m21 = m20 * M.m01 + m21 * M.m11 + m22 * M.m21;
		u.m22 = m20 * M.m02 + m21 * M.m12 + m22 * M.m22;
		*this = u;
		return *this;
	}

	template <typename X>
	const TMatrix3<X> & TMatrix3<X>::operator += (const TMatrix3<X> & M)
	{
		m00 += M.m00; m10 += M.m10; m20 += M.m20;
		m01 += M.m01; m11 += M.m11; m21 += M.m21;
		m02 += M.m02; m12 += M.m12; m22 += M.m22;
		return *this;
	}

	template <typename X>
	const TMatrix3<X> & TMatrix3<X>::operator -= (const TMatrix3<X> & M)
	{
		m00 -= M.m00; m10 -= M.m10; m20 -= M.m20;
		m01 -= M.m01; m11 -= M.m11; m21 -= M.m21;
		m02 -= M.m02; m12 -= M.m12; m22 -= M.m22;
		return *this;
	}

	template <typename X>
	const TMatrix3<X> TMatrix3<X>::operator * (const TMatrix3<X> & M) const
	{
		TMatrix3<X> u;
		u.m00 = m00 * M.m00 + m01 * M.m10 + m02 * M.m20;
		u.m01 = m00 * M.m01 + m01 * M.m11 + m02 * M.m21;
		u.m02 = m00 * M.m02 + m01 * M.m12 + m02 * M.m22;
		u.m10 = m10 * M.m00 + m11 * M.m10 + m12 * M.m20;
		u.m11 = m10 * M.m01 + m11 * M.m11 + m12 * M.m21;
		u.m12 = m10 * M.m02 + m11 * M.m12 + m12 * M.m22;
		u.m20 = m20 * M.m00 + m21 * M.m10 + m22 * M.m20;
		u.m21 = m20 * M.m01 + m21 * M.m11 + m22 * M.m21;
		u.m22 = m20 * M.m02 + m21 * M.m12 + m22 * M.m22;
		return u;
	}

	template <typename X>
	const TMatrix3<X> TMatrix3<X>::operator + (const TMatrix3<X> & M) const
	{
		TMatrix3<X> u;
		u.m00 = m00 + M.m00; u.m01 = m01 + M.m01; u.m02 = m02 + M.m02;
		u.m10 = m10 + M.m10; u.m11 = m11 + M.m11; u.m12 = m12 + M.m12;
		u.m20 = m20 + M.m20; u.m21 = m21 + M.m21; u.m22 = m22 + M.m22;
		return u;
	}

	template <typename X>
	const TMatrix3<X> TMatrix3<X>::operator - (const TMatrix3<X> & M) const
	{
		TMatrix3<X> u;
		u.m00 = m00 - M.m00; u.m01 = m01 - M.m01; u.m02 = m02 - M.m02;
		u.m10 = m10 - M.m10; u.m11 = m11 - M.m11; u.m12 = m12 - M.m12;
		u.m20 = m20 - M.m20; u.m21 = m21 - M.m21; u.m22 = m22 - M.m22;
		return u;
	}

	template <typename X>
	template <typename T>
	const TMatrix3<X> TMatrix3<X>::operator * (const T &x) const
	{
		TMatrix3<X> u;
		for (int i = 0; i < 9; i++)
			u.m[i] = m[i] * x;
		return u;
	}

	template <typename X>
	template <typename T>
	const TMatrix3<X> TMatrix3<X>::operator / (const T &x) const
	{
		TMatrix3<X> u;
		for (int i = 0; i < 9; i++)
			u = m[i] / x;
		return u;
	}

	template <typename X>
	template <typename T>
	const TMatrix3<X> &TMatrix3<X>::operator *= (const T &x)
	{
		for (int i = 0; i < 9; i++)
			m[i] *= x;
		return *this;
	}

	template <typename X>
	template <typename T>
	const TMatrix3<X> &TMatrix3<X>::operator /= (const T &x)
	{
		for (int i = 0; i < 9; i++)
			m[i] /= x;
		return *this;
	}

	template <typename X>
	template <typename T>
	void TMatrix3<X>::Rotate(const T angle)
	{
		TMatrix3<X> u;
		angle = DegToRad(angle);

		X c = cos(angle);
		X s = sin(angle);
		
		u.m[0] = c;
		u.m[1] = -s;
		u.m[2] = 0;
		
		u.m[3] = c;
		u.m[4] = s;
		u.m[5] = 0;
		
		u.m[6] = 0;
		u.m[7] = 0;
		u.m[8] = 1;
		
		*this *= u;
	}

	template <typename X>
	template <typename T>
	void TMatrix3<X>::Scale(const T x, const T y)
	{
		TMatrix3<X> u;
		
		u._11 = x; u._21 = 0; u._31 = 0;
		u._12 = 0; u._22 = y; u._32 = 0;
		u._13 = 0; u._23 = 0; u._33 = 1;
		
		*this *= u;
	}

	template <typename X>
	void TMatrix3<X>::Transpose()
	{
		TMatrix3<X> u = *this;
		
		m00 = u.t00; m10 = u.t10; u.m20 = u.t20;
		m01 = u.t01; m11 = u.t11; u.m21 = u.t21;
		m02 = u.t02; m12 = u.t12; u.m22 = u.t22;
	}

	template <typename X>
	const X TMatrix3<X>::Determinant() const
	{
		X det;
		
		det = 0;
		det += m00 * (m11 * m22 - m21 * m12);
		det -= m10 * (m01 * m22 - m21 * m02);
		det += m20 * (m01 * m12 - m11 * m02);
		
		return det;
	}

	template <typename X>
	void TMatrix3<X>::Adjugate()
	{
		X c00, c10, c20;
		X c01, c11, c21;
		X c02, c12, c22;
		
		c00 = m11 * m22 - m12 * m21;
		c10 = m02 * m21 - m01 * m22;
		c20 = m01 * m12 - m02 * m11;
		c01 = m12 * m20 - m10 * m22;
		c11 = m00 * m22 - m02 * m20;
		c21 = m02 * m10 - m00 * m12;
		c02 = m10 * m21 - m11 * m20;
		c12 = m01 * m20 - m00 * m21;
		c22 = m00 * m11 - m01 * m10;

		m00 = c00;
		m10 = c10;
		m20 = c20;
		
		m01 = c01;
		m11 = c11;
		m21 = c21;
		
		m02 = c02;
		m12 = c12;
		m22 = c22;
	}

	template <typename X>
	bool TMatrix3<X>::Invert()
	{
		X det;
		
		det = Determinant();
		if (det == 0)
			return false;
			
		Adjugate();
		*this *= X(1) / det;
		
		return true;
	}

	typedef TMatrix3<float> Matrix3f;
	typedef TMatrix3<double> Matrix3d;
}

#endif
